package cn.tk.utils;

import cn.tk.exception.CustomException;
import org.hibernate.Session;
import org.hibernate.query.NativeQuery;
import org.hibernate.query.internal.NativeQueryImpl;
import org.hibernate.transform.Transformers;
import org.hibernate.type.*;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
import org.springframework.orm.jpa.EntityManagerFactoryUtils;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.Query;
import java.lang.reflect.Constructor;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.sql.Time;
import java.sql.Timestamp;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.*;

/**
 * Created by denglw on 2018-07-24.<br/>
 * Desc: entityManager通用型查找类
 */
@Component
@Transactional(rollbackFor = Throwable.class)
public class JpaMapper {
    @PersistenceContext private EntityManager em;
    private static final Map<Class<?>, Type> JAVA2_HIBERNATE = new HashMap<>(40);

    static {
        JAVA2_HIBERNATE.put(boolean.class, StandardBasicTypes.BOOLEAN);
        JAVA2_HIBERNATE.put(byte.class, StandardBasicTypes.BYTE);
        JAVA2_HIBERNATE.put(short.class, StandardBasicTypes.SHORT);
        JAVA2_HIBERNATE.put(int.class, StandardBasicTypes.INTEGER);
        JAVA2_HIBERNATE.put(long.class, StandardBasicTypes.LONG);
        JAVA2_HIBERNATE.put(float.class, StandardBasicTypes.FLOAT);
        JAVA2_HIBERNATE.put(double.class, StandardBasicTypes.DOUBLE);
        JAVA2_HIBERNATE.put(char.class, StandardBasicTypes.CHARACTER);
        JAVA2_HIBERNATE.put(Boolean.class, StandardBasicTypes.BOOLEAN);
        JAVA2_HIBERNATE.put(Byte.class, StandardBasicTypes.BYTE);
        JAVA2_HIBERNATE.put(Short.class, StandardBasicTypes.SHORT);
        JAVA2_HIBERNATE.put(Integer.class, StandardBasicTypes.INTEGER);
        JAVA2_HIBERNATE.put(Long.class, StandardBasicTypes.LONG);
        JAVA2_HIBERNATE.put(Float.class, StandardBasicTypes.FLOAT);
        JAVA2_HIBERNATE.put(Double.class, StandardBasicTypes.DOUBLE);
        JAVA2_HIBERNATE.put(BigInteger.class, StandardBasicTypes.BIG_INTEGER);
        JAVA2_HIBERNATE.put(BigDecimal.class, StandardBasicTypes.BIG_DECIMAL);
        JAVA2_HIBERNATE.put(Character.class, StandardBasicTypes.CHARACTER);
        JAVA2_HIBERNATE.put(String.class, StandardBasicTypes.STRING);
        JAVA2_HIBERNATE.put(Date.class, StandardBasicTypes.DATE);
        JAVA2_HIBERNATE.put(Time.class, StandardBasicTypes.TIME);
        JAVA2_HIBERNATE.put(Timestamp.class, StandardBasicTypes.TIMESTAMP);
        JAVA2_HIBERNATE.put(Calendar.class, StandardBasicTypes.CALENDAR);
        JAVA2_HIBERNATE.put(TimeZone.class, StandardBasicTypes.TIMEZONE);
        JAVA2_HIBERNATE.put(LocalDate.class, LocalDateType.INSTANCE);
        JAVA2_HIBERNATE.put(LocalTime.class, LocalTimeType.INSTANCE);
        JAVA2_HIBERNATE.put(LocalDateTime.class, LocalDateTimeType.INSTANCE);
        JAVA2_HIBERNATE.put(Locale.class, StandardBasicTypes.LOCALE);
        JAVA2_HIBERNATE.put(Currency.class, StandardBasicTypes.CURRENCY);
    }

    /**
     * 通过entityManager对象构建通用型查找函数.<br/>
     *
     * @param sql    SQL查询语句
     * @param params 占位符参数
     * @param clazz  映射的结果类型
     * @param types  返回的各个字段类型
     * @return 查找的结果列表
     */
    public <T> List<T> queryList(@Nonnull String sql, @Nullable Map<String, Object> params, @Nonnull Class<T> clazz, @Nonnull Class<?>... types) {
        NativeQuery query = em.unwrap(Session.class).createNativeQuery(sql);
        if (Objects.nonNull(params) && params.size() > 0) {
            for (Map.Entry<String, Object> entry : params.entrySet()) {
                query.setParameter(entry.getKey(), entry.getValue());
            }
        }
        try {
            Constructor<T> constructor = clazz.getConstructor(types);
            LocalVariableTableParameterNameDiscoverer discoverer = new LocalVariableTableParameterNameDiscoverer();
            String[] parameterNames = discoverer.getParameterNames(constructor);
            assert parameterNames != null;
            for (int i = 0; i < parameterNames.length; i++) {
                Type hibernateType = JAVA2_HIBERNATE.getOrDefault(types[i], StandardBasicTypes.SERIALIZABLE);
                query.addScalar(parameterNames[i], hibernateType);
            }
            query.unwrap(NativeQueryImpl.class).setResultTransformer(Transformers.aliasToBean(clazz));
            //noinspection unchecked
            return EmptyUtil.nullToEmptyList(query.getResultList());
        } catch (NoSuchMethodException e) {
            throw new CustomException("没有匹配types的构造器: " + e.getMessage());
        } finally {
            em.clear();
            EntityManagerFactoryUtils.closeEntityManager(em);
        }
    }

    /**
     * 查找单个实体对象.<br/>
     *
     * @param sql    SQL查询语句
     * @param params 占位符参数
     * @param clazz  映射的结果类型
     * @param types  返回的各个字段类型
     * @return 查找的结果optional
     */
    public <T> Optional<T> queryOne(@Nonnull String sql, @Nullable Map<String, Object> params, @Nonnull Class<T> clazz, @Nonnull Class<?>... types) {
        List<T> queryList = queryList(sql, params, clazz, types);
        if (queryList.isEmpty()) {
            return Optional.empty();
        }
        return Optional.ofNullable(queryList.get(0));
    }

    /**
     * 单个数据查询，获取结果对象.<br/>
     *
     * @param sql    sql语句
     * @param params 占位符参数
     * @return 单个结果对象
     */
    public Object single(@Nonnull String sql, @Nullable Map<String, Object> params) {
        Query query = em.createNativeQuery(sql);
        if (Objects.nonNull(params) && params.size() > 0) {
            for (Map.Entry<String, Object> entry : params.entrySet()) {
                query.setParameter(entry.getKey(), entry.getValue());
            }
        }
        return query.getSingleResult();
    }

    /**
     * 数量统计查询，获取查询结果的数量值.<br/>
     *
     * @param sql    sql语句
     * @param params 占位符参数map
     * @return 查询到的数量值
     */
    public BigInteger count(@Nonnull String sql, @Nullable Map<String, Object> params) {
        Object result = single(sql, params);
        if (Objects.isNull(result)) {
            return null;
        }
        return (BigInteger) result;
    }

}
